﻿namespace MetaProperties.Core
{
    using System;
    using System.Collections.Generic;

    /// <summary>
    /// A filter pipeline which isolates both the event filters and each individual event handler.
    /// </summary>
    public class IsolatingFilterPipeline : IFilterPipeline
    {
        /// <summary>
        /// The total number of event filters.
        /// </summary>
        private readonly int filterCount;

        /// <summary>
        /// The action which will execute an event handler.
        /// </summary>
        private readonly Action<object, EventArgs, Delegate> executeAction;

        /// <summary>
        /// A delegate containing the event handlers.
        /// </summary>
        private readonly Delegate handlers;

        /// <summary>
        /// The list of event filters.
        /// </summary>
        private readonly IList<IEventFilter> filters;

        /// <summary>
        /// The index of the next event filter to be used.
        /// </summary>
        private int filterIndex;

        /// <summary>
        /// Initializes a new instance of the <see cref="IsolatingFilterPipeline"/> class.
        /// </summary>
        /// <param name="handlers">The event handlers.</param>
        /// <param name="filters">The event filters.</param>
        /// <param name="executeAction">The action which will execute a given event handler.</param>
        internal IsolatingFilterPipeline(Delegate handlers, IList<IEventFilter> filters, Action<object, EventArgs, Delegate> executeAction)
        {
            this.executeAction = executeAction;
            this.handlers = handlers;

            this.filters = filters;
            this.filterCount = filters == null ? 0 : filters.Count;
        }

        /// <summary>
        /// Executes the specified event handlers.
        /// </summary>
        /// <param name="handlers">The event handlers.</param>
        /// <param name="filters">The event filters.</param>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        /// <param name="executeAction">The execute action.</param>
        public static void Execute(
            Delegate handlers, 
            IList<IEventFilter> filters, 
            object sender, 
            EventArgs args,
            Action<object, EventArgs, Delegate> executeAction)
        {
            try
            {
                IFilterPipeline pipeline = new IsolatingFilterPipeline(handlers, filters, executeAction);
                pipeline.Resume(sender, args);
            }
            catch (Exception t)
            {
                if (!IsolatingEventFilter.HandleException(t))
                {
                    throw;
                }
            }
        }

        /// <summary>
        /// Resumes the event pipeline, performing any remaining filtering.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        public void Resume(object sender, EventArgs args)
        {
            if (this.filterCount == this.filterIndex)
            {
                Delegate[] subHandlers = this.handlers.GetInvocationList();
                int subHandlerCount = subHandlers.Length;

                for (int i = 0; i < subHandlerCount; i++)
                {
                    try
                    {
                        this.executeAction(sender, args, subHandlers[i]);
                    }
                    catch (Exception t)
                    {
                        if (!IsolatingEventFilter.HandleException(t))
                        {
                            throw;
                        }
                    }
                }
            }
            else
            {
                this.filters[this.filterIndex++]
                    .PerformFilter(sender, args, this);
            }
        }
    }
}
